<?php
/**
 * checkcode.libaray.php
 * @Copyright lemonCarlJ All rights reserved.
 * @License http://www.56code.com/License.txt
 * @Version 1.0.0
 * @Email rpgshifa@aliyun.com
 */
namespace SF\libaray;

class verify_code
{
    /**
     * 字符串
     * @var string
     */
    public $string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    
    /**
     * 长度
     * @var int
     */
    public $length = 4;
    
    /**
     * 字体样式
     * @var string
     */
    public $font_family = SF . 'cour.ttf';
    
    /**
     * 字体大小
     * @var int
     */
    public $font_size = 16;
    
    /**
     * 字体颜色
     * @var array
     */
    public $font_color = array( 0, 0, 0 );
    
    /**
     * 宽度
     * @var int
     */
    public $width = 150;
    
    /**
     * 高度
     * @var int
     */
    public $height = 50;
    
    /**
     * 混淆码
     * @var string
     */
    public $confused = null;
    
    /**
     * 干扰等级
     * @var integer
     */
    public $level = 1;
    
    /**
     * 验证码图片
     * @var resource|null
     */
    public $image = null;
    
    /**
     * 验证码字符
     * @var null|string
     */
    public $code = null;
    
    /**
     * 生成字符
     * @param string 筛选字符串
     * @param number $length 生成长度
     * @return array 返回指定长度随机生成的字符串
     */
    protected function code( $string, $length )
    {
        //返回字符
        $code = array();
        
        //总数
        $total = mb_strlen( $string, 'UTF-8' );
        
        //生成
        for ( $i = 0; $i < $length; $i ++ )
        {
            $code[] = mb_substr( $string, mt_rand( 0, $total ) - 1, 1 );
        }
        
        return $code;
    }
    
    /**
     * 创建画板
     * @param int $width 宽度
     * @param int $height 高度
     * @return resource 返回画板句柄
     */
    protected function canvas( $width, $height )
    {
        //创建画布
        $image = imagecreatetruecolor( $width, $height );
        
        //背景填充白色
        $background = imagecolorallocate( $image, 255, 255, 255 );
        
        //开始填充
        imagefill( $image, 0, 0, $background);
        
        return $image;
    }
    
    /**
     * 画入验证码
     * @param resource $image 图像资源句柄
     * @param int $width 画板宽度
     * @param int $height 画板高度
     * @param string $code 画入的验证码
     * @param int $font_size 字体大小
     * @param string $font_family 字体种类
     * @param array $font_color 字体颜色
     * @param int $level 扰乱等级
     * @return boolean 返回每个字符点位信息
     * @return boolean|array 返回点位信息或者生成失败
     */
    protected function draw_code( $image, $width, $height, $code, $font_size, $font_family, $font_color, $level )
    {
        //是否有字体库
        if( ! file_exists( $font_family ) )
        {
            return false;
        }
        
        //随机率
        $rand_rate = round( 3 / $level );
        
        //透明度
        $transparent = $level * 20;
        
        //字数
        $count = count( $code );
        
        //列宽
        $column_width = $width / $count;
        
        //点位信息
        $info = array();
        
        //背景色
        if( $level > 1 ){
            if( $level == 2 ){
                $background_color = imagecolorallocate( $image, mt_rand( 200, 255 ), mt_rand( 200, 255 ), mt_rand( 200, 255 ) );
                imagefill( $image, 0, 0, $background_color);
            }else{
                //渐变色
                for ( $i = 0; $i < $width; $i++ )
                {
                    $background_color = imagecolorallocate( $image, 255 - $i, 255 - $i, 255 - $i );
                    imagefilledrectangle( $image, $i, $height, $i + 1, 0, $background_color );
                }
            }
        }
        
        for( $i = 1; $i <= $count; $i ++ )
        {
            //字体取色
            if( ! is_array( $font_color ) )
            {
                $basic_color = array();
                $basic_color[] = mt_rand( 0, 200 );
                $basic_color[] = mt_rand( 0, 200 );
                $basic_color[] = mt_rand( 0, 200 );
            }else{
                $basic_color = $font_color;
            }

            //随机是否透明
            $is_transparent = mt_rand( 0, $rand_rate ) === 0 ? true : false;
            if( $is_transparent )
            {
                if( $transparent > 127 )
                {
                    $transparent = 127;
                }
                $color = imagecolorallocatealpha( $image, $basic_color[ 0 ], $basic_color[ 1 ], $basic_color[ 2 ], $transparent );
            }else{
                $color = imagecolorallocate( $image, $basic_color[ 0 ], $basic_color[ 1 ], $basic_color[ 2 ] );
            }
            
            //旋转角度
            $angle = mt_rand( -30, 30 );
            
            //真实字体大小
            $real_font_size = $font_size + mt_rand( 0, $rand_rate );
            
            //当前字体位置
            $x = $column_width * $i - ( $column_width- $real_font_size ) / 2;
            
            $y = $height / 2 + $real_font_size / 2;
            
            //单验证码字符
            $verify_code = $code[ $i - 1 ];
            
            imagettftext( $image, $real_font_size, $angle, $x, $y, $color, $font_family, $verify_code );
            
            $info[] = array( $verify_code, $x, $y, $real_font_size, $basic_color );
        }
        
        return $info;
    }
    
    /**
     * 画入混淆
     * @param resource $image 图像资源句柄
     * @param int $width 画板宽度
     * @param int $height 画板高度
     * @param string $confused 扰乱码
     * @param array $info 验证码点位信息
     * @param int $level 扰乱等级
     */
    protected function draw_confused( $image, $width, $height, $confused, $info, $level )
    {
        //基准点数
        $basic_point = 10;
        
        //总数
        $count = count( $info );
        
        //列宽
        $column_width = $width / $count;
        
        //计数
        $j = 1;
        
        foreach ( $info as $code )
        {
            $basic_color = $code[ 4 ];
            
            //中心位置
            $x = $code[ 1 ];
            
            $y = $code[ 2 ];
            
            //字体大小
            $font_size = $code[ 3 ];
            
            //噪点
            for( $i = 0; $i < ( $level * $basic_point ); $i ++ )
            {
                //噪点位置
                $noise_locate = array(
                    mt_rand( $column_width * ( $j - 1 ), $column_width * $j ),
                    mt_rand( 0, $height )
                );
                
                //距离修正
                if( $level > 1 ){
                    //x轴修正
                    if( ( $x_distance = ( $noise_locate[ 0 ] - $x ) ) > 0 )
                    {
                        $x_distance -= $x_distance / ( $level / 2 );
                        $noise_locate[ 0 ] -= $x_distance;
                    }else{
                        $x_distance = abs( $x_distance );
                        $x_distance -= $x_distance / ( $level / 2 );
                        $noise_locate[ 0 ] += $x_distance;
                    }
                    
                    //y轴修正
                    if( ( $y_distance = ( $noise_locate[ 1 ] - $y ) ) > 0 )
                    {
                        $y_distance -= $y_distance / ( $level / 2 );
                        $noise_locate[ 1 ] -= $y_distance;
                    }else{
                        $y_distance = abs( $y_distance );
                        $y_distance -= $y_distance / ( $level / 2 );
                        $noise_locate[ 1 ] += $y_distance;
                    }
                }
                
                $noise_color = imagecolorallocate( $image, $basic_color[ 0 ], $basic_color[ 1 ], $basic_color[ 2 ] );
                imagesetpixel( $image, $noise_locate[ 0 ], $noise_locate[ 1 ], $noise_color );
            }
            
            //线条
            if( $level > 1 ){
                for ( $i = 0; $i < $level - 1; $i++ ) {
                    $color = imagecolorallocate( $image, $basic_color[ 0 ], $basic_color[ 1 ], $basic_color[ 2 ] );
                    imageline( $image, mt_rand( 0, $width ), mt_rand( 0, $height ), mt_rand( 0, $width ), mt_rand( 0, $height ) , $color);
                }
            }
            
            //干扰字符
            if( $level > 2 ){
                imagestring( $image, mt_rand( 1, 5 ), mt_rand( 0, $width ), mt_rand( 0, $height ), $confused[ $j - 1 ], imagecolorallocate( $image, 255, 255, 255 ) );
            }
            
            //干扰曲线
            if( $level > 2 ){
                for( $i = 0; $i < $width; $i ++ )
                {
                    $sin = ( $height / 2 ) * sin( ( $i / 2 ) / ( $height  ) * ( M_PI / 2 ) );
                    imageSetPixel( $image, $i, $sin, imagecolorallocate( $image, 0, 0, 0 ) );
                }
            }
            
            $j ++;
        }
        
        
    }
    
    /**
     * 生成验证码
     * @return boolean 返回是否创建成功
     */
    public function create()
    {
        //获取字符集
        $string = $this->string;
        
        //获取长度
        $length = $this->length;
        
        //获取宽度
        $width = $this->width;
        
        //获取高度
        $height = $this->height;
        
        //字体大小
        $font_size = $this->font_size;
        
        //字体种类
        $font_family = $this->font_family;
        
        //字体颜色
        $font_color = $this->font_color;
        
        //混淆等级
        $level = $this->level;
        
        //生成验证码
        $code = $this->code( $string, $length );
        
        //生成画板
        if( $image = $this->canvas( $width, $height ) )
        {
            //画入验证码
            if( $info = $this->draw_code( $image, $width, $height, $code, $font_size, $font_family, $font_color, $level ) )
            {
                //获取混淆码
                if( ! $confused = $this->confused )
                {
                    $confused = $string;
                }
                
                //抽取等量随机码
                $confused = $this->code( $confused, $length );
                
                //画入混淆
                $this->draw_confused( $image, $width, $height, $confused, $info, $level );
                
                //写入验证码
                $this->code = implode( '', $code );
                
                //写入验证码图像
                $this->image = $image;
                
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 输出验证码
     * @param string $type 输出图片类型 [ 'png' ]
     * @return resource 返回图片资源
     */
    public function export( $type = 'png' )
    {
        header( 'Content-type:image/' . $type );
        $image_function = 'image' . $type;
        $image_function( $this->image );
        imagedestroy( $this->image );
    }
}